package weka.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Vector;

import weka.classifiers.Classifier;

public class DiversityMeasureImplementation implements
        AdditionalMeasureProducer, Serializable {

    private boolean[][] correctness;
    private double[][] dCorrectness;
    private double[][] predictions;
    private double[][] nMatrix;
    private double[][] n11Matrix;
    private double[][] n10Matrix;
    private double[][] n01Matrix;
    private double[][] n00Matrix;
    private double[][] n00sMatrix;
    private double[][] n00dMatrix;
    private Instances data;
    private Classifier classifier;
    private List<Classifier> baseClassifiers;
    private List<Integer> indices;
    private double N;
    private double L;
    private int C;
    private boolean initialized = false;
    private boolean measureOutOfBag = false;
    private boolean[][] inBag;
    private double weight = 5;
    private boolean useCompound = false;

    public DiversityMeasureImplementation() {
    }

    public DiversityMeasureImplementation(Classifier classifier, Instances data)
            throws Exception {
        if (!classifier.isEnsemble()
                || classifier.getEnsembleMembers().size() <= 1) {
            return;
        }
        this.data = data;
        this.classifier = classifier;
        this.baseClassifiers = classifier.getEnsembleMembers();
        N = data.numInstances();
        L = baseClassifiers.size();
        C = data.numClasses();
        initializePredictions();
        initializeCorrectness();
        initializeNMatrix();
        initialized = true;
    }

    public DiversityMeasureImplementation(Classifier classifier,
            Instances data, boolean[][] bag) throws Exception {
        if (!classifier.isEnsemble()
                || classifier.getEnsembleMembers().size() <= 1) {
            return;
        }
        this.data = data;
        this.classifier = classifier;
        this.baseClassifiers = classifier.getEnsembleMembers();
        N = data.numInstances();
        L = baseClassifiers.size();
        C = data.numClasses();
        initializePredictions();
        initializeCorrectness();
        initializeNMatrix(inBag);
        initialized = true;
        measureOutOfBag = true;
        inBag = new boolean[(int) N][(int) L];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < L; j++) {
                inBag[i][j] = bag[j][i];
            }
        }
    }

    public void initializeNMatrix(boolean[][] inBag) {
        nMatrix = new double[(int) (L - 1)][(int) L];
        n11Matrix = new double[(int) (L - 1)][(int) L];
        n10Matrix = new double[(int) (L - 1)][(int) L];
        n01Matrix = new double[(int) (L - 1)][(int) L];
        n00Matrix = new double[(int) (L - 1)][(int) L];
        n00sMatrix = new double[(int) (L - 1)][(int) L];
        n00dMatrix = new double[(int) (L - 1)][(int) L];

        indices = new ArrayList<Integer>((int) L);
        for (int i = 0; i < L; i++) {
            indices.add(i);
        }
        for (Collection<Integer> c : new CombinatorialIterator<Integer>(2,
                indices)) {
            Iterator<Integer> iter = c.iterator();
            int idx1 = iter.next(), idx2 = iter.next();
            nMatrix[idx1][idx2] = 0;
            n11Matrix[idx1][idx2] = 0;
            n10Matrix[idx1][idx2] = 0;
            n01Matrix[idx1][idx2] = 0;
            n00Matrix[idx1][idx2] = 0;
            n00sMatrix[idx1][idx2] = 0;
            n00dMatrix[idx1][idx2] = 0;
            for (int i = 0; i < N; i++) {
                if (measureOutOfBag) {
                    if (inBag[i][idx1] | inBag[i][idx2]) {
                        continue;
                    }
                }
                nMatrix[idx1][idx2]++;
                if (correctness[i][idx1] == correctness[i][idx2]
                        && correctness[i][idx1]) {
                    n11Matrix[idx1][idx2]++;
                } else if (correctness[i][idx1] == correctness[i][idx2]
                        && !correctness[i][idx1]) {
                    n00Matrix[idx1][idx2]++;
                    if (predictions[i][idx1] == predictions[i][idx2]) {
                        n00sMatrix[idx1][idx2]++;
                    } else {
                        n00dMatrix[idx1][idx2]++;
                    }
                } else if (correctness[i][idx1] != correctness[i][idx2]
                        && correctness[i][idx1]) {
                    n10Matrix[idx1][idx2]++;
                } else if (correctness[i][idx1] != correctness[i][idx2]
                        && !correctness[i][idx1]) {
                    n01Matrix[idx1][idx2]++;
                }
            }
        }
    }

    public void initializeNMatrix() {
        n11Matrix = new double[(int) L][(int) L];
        n10Matrix = new double[(int) L][(int) L];
        n01Matrix = new double[(int) L][(int) L];
        n00Matrix = new double[(int) L][(int) L];
        n00sMatrix = new double[(int) L][(int) L];
        n00dMatrix = new double[(int) L][(int) L];

        indices = new ArrayList<Integer>((int) L);
        for (int i = 0; i < L; i++) {
            indices.add(i);
        }
        for (Collection<Integer> c : new CombinatorialIterator<Integer>(2,
                indices)) {
            Iterator<Integer> iter = c.iterator();
            int idx1 = iter.next(), idx2 = iter.next();
            n11Matrix[idx1][idx2] = 0;
            n10Matrix[idx1][idx2] = 0;
            n01Matrix[idx1][idx2] = 0;
            n00Matrix[idx1][idx2] = 0;
            n00sMatrix[idx1][idx2] = 0;
            n00dMatrix[idx1][idx2] = 0;
            for (int i = 0; i < N; i++) {
                if (correctness[i][idx1] == correctness[i][idx2]
                        && correctness[i][idx1]) {
                    n11Matrix[idx1][idx2]++;
                } else if (correctness[i][idx1] == correctness[i][idx2]
                        && !correctness[i][idx1]) {
                    n00Matrix[idx1][idx2]++;
                    if (predictions[i][idx1] == predictions[i][idx2]) {
                        n00sMatrix[idx1][idx2]++;
                    } else {
                        n00dMatrix[idx1][idx2]++;
                    }
                } else if (correctness[i][idx1] != correctness[i][idx2]
                        && correctness[i][idx1]) {
                    n10Matrix[idx1][idx2]++;
                } else if (correctness[i][idx1] != correctness[i][idx2]
                        && !correctness[i][idx1]) {
                    n01Matrix[idx1][idx2]++;
                }
            }
            n11Matrix[idx2][idx1] = n11Matrix[idx1][idx2];
            n10Matrix[idx2][idx1] = n10Matrix[idx1][idx2];
            n01Matrix[idx2][idx1] = n01Matrix[idx1][idx2];
            n00Matrix[idx2][idx1] = n00Matrix[idx1][idx2];
            n00sMatrix[idx2][idx1] = n00sMatrix[idx1][idx2];
            n00dMatrix[idx2][idx1] = n00dMatrix[idx1][idx2];
        }
    }

    public void initializePredictions() throws Exception {
        predictions = new double[(int) N][(int) L];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < L; j++) {
                predictions[i][j] = baseClassifiers.get(j).classifyInstance(
                        data.instance(i));
            }
        }
    }

    public void initializeCorrectness() {
        correctness = new boolean[(int) N][(int) L];
        dCorrectness = new double[(int) N][(int) L];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < L; j++) {
                correctness[i][j] = predictions[i][j] == data.instance(i).classValue();
                if (correctness[i][j]) {
                    dCorrectness[i][j] = 1;
                } else {
                    dCorrectness[i][j] = 0;
                }
            }
        }
    }

    public double avg(double[][] values) {
        double sum = 0;
        for (int i = 0; i < L - 1; i++) {
            for (int k = i + 1; k < L; k++) {
                sum += values[i][k];
            }
        }

        return (2.0 / (1.0 * L * (1.0 * L - 1.0))) * sum;
    }

    public double Compound(String diversity) {
        double accuracyTerm = 1, diversityTerm = 1, div = 0;
        for (int i = 0; i < L; i++) {
            accuracyTerm *= 1 - getBaseAccuracy(i);
            for (int j = i+1; j < L; j++) {
                diversityTerm *= getDMeasure(diversity, i, j);
            }
        }
        accuracyTerm = Math.pow(accuracyTerm, 1 / L);
        diversityTerm = Math.pow(diversityTerm, 1 / (L * (L - 1)));
        div = accuracyTerm * diversityTerm;
        return (L / (L - 1)) * div;
    }

    private double getBaseAccuracy(int classifier) {
        double accuracy = 0;
        for (int i = 0; i < N; i++) {
            accuracy += dCorrectness[i][classifier];
        }
        return accuracy / N;
    }

    public double getDMeasure(String measure, int i, int j) {
        if (measure.equalsIgnoreCase("Q")) {
            return 1 - measureQ(i, j);
        } else if (measure.equalsIgnoreCase("P")) {
            return 1 - measureP(i, j);
        } else if (measure.equalsIgnoreCase("Disagreement")) {
            return measureDisagreement(i, j);
        } else if (measure.equalsIgnoreCase("Doublefault")) {
            return 1 - measureDoubleFault(i, j);
        } else if (measure.equalsIgnoreCase("Entropy")) {
            return measureEntropy(i, j);
        } else if (measure.equalsIgnoreCase("Kohaviwolpert")) {
            return 1 - measureKohaviWolpert(i, j);
        } else if (measure.equalsIgnoreCase("InterraterK")) {
            return measureInterraterK(i, j);
        } else if (measure.equalsIgnoreCase("Difficulty")) {
            return 1 - measureDifficulty(i, j);
        } else if (measure.equalsIgnoreCase("GeneralizedDiversity")) {
            return measureGeneralizedDiversity(i, j);
        } else if (measure.equalsIgnoreCase("CoincidenceFailureDiversity")) {
            return measureCoincidenceFailureDiversity(i, j);
        } else if (measure.equalsIgnoreCase("DistinctFailureDiversity")) {
            return measureDistinctFailureDiversity(i, j);
        } else if (measure.equalsIgnoreCase("Samefault")) {
            return 1 - measureSameFault(i, j);
        } else if (measure.equalsIgnoreCase("WeightedCountErrorCorrect")) {
            return measureWeightedCountErrorCorrect(i, j);
        }
        return 0;
    }

    public double measureDisagreement() {
        double[][] values = new double[(int) (L - 1)][(int) L];

        for (int idx1 = 0; idx1 < L - 1; idx1++) {
            for (int idx2 = idx1 + 1; idx2 < L; idx2++) {
                values[idx1][idx2] = measureDisagreement(idx1, idx2);
            }
        }
        return avg(values);
    }

    public double measureDisagreement(int idx1, int idx2) {
        if (measureOutOfBag)
            return ((n10Matrix[idx1][idx2] + n01Matrix[idx1][idx2])) / nMatrix[idx1][idx2];
        else
            return ((n10Matrix[idx1][idx2] + n01Matrix[idx1][idx2])) / N;
    }

    public double measureDoubleFault() {
        double[][] values = new double[(int) (L - 1)][(int) L];

        for (int idx1 = 0; idx1 < L - 1; idx1++) {
            for (int idx2 = idx1 + 1; idx2 < L; idx2++) {
                values[idx1][idx2] = measureDoubleFault(idx1, idx2);
            }
        }
        return avg(values);
    }

    public double measureDoubleFault(int idx1, int idx2) {
        if (measureOutOfBag) {
            return (n00Matrix[idx1][idx2])
                    / nMatrix[idx1][idx2];
        } else {
            return (n00Matrix[idx1][idx2]) / N;
        }
    }

    public double measureQ() {
        double[][] values = new double[(int) (L - 1)][(int) L];

        for (int idx1 = 0; idx1 < L - 1; idx1++) {
            for (int idx2 = idx1 + 1; idx2 < L; idx2++) {
                values[idx1][idx2] = measureQ(idx1, idx2);
            }
        }
        return avg(values);
    }

    public double measureQ(int idx1, int idx2) {
        double denominator = ((n11Matrix[idx1][idx2] * n00Matrix[idx1][idx2]) + (n01Matrix[idx1][idx2] * n10Matrix[idx1][idx2]));

        if (denominator == 0) {
            return 0;
        } else {
            return ((n11Matrix[idx1][idx2] * n00Matrix[idx1][idx2]) - (n01Matrix[idx1][idx2] * n10Matrix[idx1][idx2]))
                    / denominator;
        }
    }

    public double measureP() {
        double[][] values = new double[(int) (L - 1)][(int) L];

        for (int idx1 = 0; idx1 < L - 1; idx1++) {
            for (int idx2 = idx1 + 1; idx2 < L; idx2++) {
                double denominator = Math.sqrt((n11Matrix[idx1][idx2] + n10Matrix[idx1][idx2])
                        * (n11Matrix[idx1][idx2] + n01Matrix[idx1][idx2])
                        * (n00Matrix[idx1][idx2] + n10Matrix[idx1][idx2])
                        * (n00Matrix[idx1][idx2] + n01Matrix[idx1][idx2]));
                if (denominator == 0) {
                    values[idx1][idx2] = 0;
                } else {
                    values[idx1][idx2] = ((n11Matrix[idx1][idx2] * n00Matrix[idx1][idx2]) - (n01Matrix[idx1][idx2] * n10Matrix[idx1][idx2]))
                            / denominator;
                }
            }
        }
        return avg(values);
    }

    public double measureP(int idx1, int idx2) {
        double denominator = Math.sqrt((n11Matrix[idx1][idx2] + n10Matrix[idx1][idx2])
                * (n11Matrix[idx1][idx2] + n01Matrix[idx1][idx2])
                * (n00Matrix[idx1][idx2] + n10Matrix[idx1][idx2])
                * (n00Matrix[idx1][idx2] + n01Matrix[idx1][idx2]));
        if (denominator == 0) {
            return 0;
        } else {
            return ((n11Matrix[idx1][idx2] * n00Matrix[idx1][idx2]) - (n01Matrix[idx1][idx2] * n10Matrix[idx1][idx2]))
                    / denominator;
        }
    }

    public double measureEntropy() {
        if (measureOutOfBag) {
            double lz, l, e = 0;
            for (int i = 0; i < N; i++) {
                l = 0;
                lz = 0;
                for (int j = 0; j < L; j++) {
                    if (!inBag[i][j]) {
                        lz += dCorrectness[i][j];
                        l++;
                    }
                }
                if (l > 0) {
                    e += (1 / (l - Math.floor(l / 2))) * Math.min(lz, l - lz);
                }
            }
            return (1 / N) * e;
        } else {
            double[] lz = new double[(int) N];
            double e = 0;
            for (int i = 0; i < N; i++) {
                lz[i] = Utils.sum(dCorrectness[i]);
                e += (1 / (L - Math.floor(L / 2))) * Math.min(lz[i], L - lz[i]);
            }
            return (1 / N) * e;
        }
    }

    public double measureEntropy(int im, int jm) {
        double L = 2;
        double[] lz = new double[(int) N];
        double e = 0;
        for (int i = 0; i < N; i++) {
            lz[i] = dCorrectness[i][im] + dCorrectness[i][jm];
            e += (1 / (L - Math.floor(L / 2))) * Math.min(lz[i], L - lz[i]);
        }
        return (1 / N) * e;
    }

    public double measureInterraterK() {
        if (measureOutOfBag) {
            double[] lz = new double[(int) N];
            double phat, k = 0;
            for (int i = 0; i < N; i++) {
                double l = 0;
                lz[i] = 0;
                for (int j = 0; j < L; j++) {
                    if (!inBag[i][j]) {
                        lz[i] += dCorrectness[i][j];
                        l++;
                    }
                }
                k += lz[i] * (l - lz[i]);
            }
            phat = (1 / (N * L)) * Utils.sum(lz);
            return 1 - ((1 / L) * k) / (N * (L - 1) * phat * (1 - phat));
        } else {
            double[] lz = new double[(int) N];
            double phat, k = 0;
            for (int i = 0; i < N; i++) {
                lz[i] = Utils.sum(dCorrectness[i]);
                k += lz[i] * (L - lz[i]);
            }
            phat = (1 / (N * L)) * Utils.sum(lz);
            return 1 - ((1 / L) * k) / (N * (L - 1) * phat * (1 - phat));
        }
    }

    public double measureInterraterK(int im, int jm) {
        double L = 2;
        double[] lz = new double[(int) N];
        double phat, k = 0;
        for (int i = 0; i < N; i++) {
            lz[i] = dCorrectness[i][im] + dCorrectness[i][jm];
            k += lz[i] * (L - lz[i]);
        }
        phat = (1 / (N * L)) * Utils.sum(lz);
        return 1 - ((1 / L) * k) / (N * (L - 1) * phat * (1 - phat));
    }

    public double measureKohaviWolpert() {
        if (measureOutOfBag) {
            double lz, l, kw = 0;
            for (int i = 0; i < N; i++) {
                lz = 0;
                l = 0;
                for (int j = 0; j < L; j++) {
                    if (!inBag[i][j]) {
                        lz += dCorrectness[i][j];
                        l++;
                    }
                }
                kw += lz * (l - lz);
            }
            return (1 / (N * L * L)) * kw;
        } else {
            double lz, kw = 0;
            for (int i = 0; i < N; i++) {
                lz = Utils.sum(dCorrectness[i]);
                kw += lz * (L - lz);
            }
            return (1 / (N * L * L)) * kw;
        }
    }

    public double measureKohaviWolpert(int im, int jm) {
        double L = 2;
        double lz, kw = 0;
        for (int i = 0; i < N; i++) {
            lz = dCorrectness[i][im] + dCorrectness[i][jm];
            kw += lz * (L - lz);
        }
        return (1 / (N * L * L)) * kw;
    }

    public double measureDifficulty() {
        if (measureOutOfBag) {
            double[] sum = new double[(int) N];
            for (int i = 0; i < N; i++) {
                double l = 0;
                for (int j = 0; j < L; j++) {
                    if (!inBag[i][j]) {
                        sum[i] += dCorrectness[i][j];
                        l++;
                    }
                }
                if (l > 0) {
                    sum[i] /= l;
                }
            }
            return Utils.variance(sum);
        } else {
            double[] sum = new double[(int) N];
            for (int i = 0; i < N; i++) {
                sum[i] = Utils.sum(dCorrectness[i]) / L;
            }
            return Utils.variance(sum);
        }
    }

    public double measureDifficulty(int im, int jm) {
        double L = 2;
        double[] sum = new double[(int) N];
        for (int i = 0; i < N; i++) {
            sum[i] = (dCorrectness[i][im] + dCorrectness[i][jm]) / L;
        }
        return Utils.variance(sum);
    }

    public double measureGeneralizedDiversity() {
        if (measureOutOfBag) {
            double[] Y = new double[(int) N];
            double[] nVector = new double[(int) L];
            double[] p = new double[(int) L + 1];
            double p1 = 0, p2 = 0;
            for (int i = 0; i < N; i++) {
                double l = 0, sum = 0;
                for (int j = 0; j < L; j++) {
                    if (!inBag[i][j]) {
                        sum += dCorrectness[i][j];
                        l++;
                        nVector[j]++;
                    }
                }
                Y[i] = l - sum;
                p[(int) Y[i]]++;
            }
            for (int j = 1; j < L + 1; j++) {
                if (nVector[j - 1] == 0) {
                    p[j] = 0;
                } else {
                    p[j] = p[j] / nVector[j - 1];
                }
                p1 += (j / L) * p[j];
                p2 += (j / L) * ((j - 1) / (L - 1)) * p[j];
            }
            if (p1 == 0) {
                return 0;
            }
            return 1 - p2 / p1;
        } else {
            double[] Y = new double[(int) N];
            double[] p = new double[(int) L + 1];
            double p1 = 0, p2 = 0;
            for (int i = 0; i < N; i++) {
                Y[i] = L - Utils.sum(dCorrectness[i]);
                p[(int) Y[i]]++;
            }
            for (int j = 1; j < L + 1; j++) {
                p[j] = p[j] / N;
                p1 += (j / L) * p[j];
                p2 += (j / L) * ((j - 1) / (L - 1)) * p[j];
            }
            if (p1 == 0) {
                return 0;
            }
            return 1 - p2 / p1;
        }
    }

    public double measureGeneralizedDiversity(int im, int jm) {
        double L = 2;
        double[] Y = new double[(int) N];
        double[] p = new double[(int) L + 1];
        double p1 = 0, p2 = 0;
        for (int i = 0; i < N; i++) {
            Y[i] = L - (dCorrectness[i][im] + dCorrectness[i][jm]);
            p[(int) Y[i]]++;
        }
        for (int j = 1; j < L + 1; j++) {
            p[j] = p[j] / N;
            p1 += (j / L) * p[j];
            p2 += (j / L) * ((j - 1) / (L - 1)) * p[j];
        }
        if (p1 == 0) {
            return 0;
        }
        return 1 - p2 / p1;
    }

    public double measureCoincidenceFailureDiversity() {
        if (measureOutOfBag) {
            double[] Y = new double[(int) N];
            double[] nVector = new double[(int) L];
            double[] p = new double[(int) L + 1];
            double p0 = 0, cfd = 0;
            for (int i = 0; i < N; i++) {
                double l = 0, sum = 0;
                for (int j = 0; j < L; j++) {
                    if (!inBag[i][j]) {
                        sum += dCorrectness[i][j];
                        l++;
                        nVector[j]++;
                    }
                }
                Y[i] = l - sum;
                p[(int) Y[i]]++;
            }
            if (nVector[0] == 0) {
                return 0;
            }
            p0 = p[0] / nVector[0];
            if (p0 == 1) {
                return 0;
            }
            for (int j = 1; j < L + 1; j++) {
                if (nVector[j - 1] == 0) {
                    p[j] = 0;
                } else {
                    p[j] = p[j] / nVector[j - 1];
                }
                cfd += ((L - j) / (L - 1)) * p[j];
            }
            return (1 / (1 - p0)) * cfd;
        } else {
            double[] Y = new double[(int) N];
            double[] p = new double[(int) L + 1];
            double p0 = 0, cfd = 0;
            for (int i = 0; i < N; i++) {
                Y[i] = L - Utils.sum(dCorrectness[i]);
                p[(int) Y[i]]++;
            }
            p0 = p[0] / N;
            if (p0 == 1) {
                return 0;
            }
            for (int j = 1; j < L + 1; j++) {
                p[j] = p[j] / N;
                cfd += ((L - j) / (L - 1)) * p[j];
            }
            return (1 / (1 - p0)) * cfd;
        }
    }

    public double measureCoincidenceFailureDiversity(int im, int jm) {
        double L = 2;
            double[] Y = new double[(int) N];
            double[] p = new double[(int) L + 1];
            double p0 = 0, cfd = 0;
            for (int i = 0; i < N; i++) {
                Y[i] = L - (dCorrectness[i][im] + dCorrectness[i][jm]);
                p[(int) Y[i]]++;
            }
            p0 = p[0] / N;
            if (p0 == 1) {
                return 0;
            }
            for (int j = 1; j < L + 1; j++) {
                p[j] = p[j] / N;
                cfd += ((L - j) / (L - 1)) * p[j];
            }
            return (1 / (1 - p0)) * cfd;
    }

    public double measureDistinctFailureDiversity() {
        if (measureOutOfBag) {
            double[][] faults = new double[(int) N][C];
            double[] t = new double[(int) L];
            double totalFaults = 0, df = 0;

            for (int j = 0; j < L; j++) {
                t[j] = 0;
            }
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < C; j++) {
                    faults[i][j] = 0;
                }
                for (int j = 0; j < L; j++) {
                    if (!inBag[i][j]) {
                        if (!correctness[i][j]) {
                            faults[i][(int) predictions[i][j]]++;
                        }
                    }
                }
                for (int j = 0; j < C; j++) {
                    if (faults[i][j] != 0) {
                        totalFaults++;
                        t[(int) faults[i][j] - 1]++;
                    }
                }
            }
            for (int j = 0; j < L; j++) {
                t[j] = t[j] / totalFaults;
                df += ((L - j) / (L - 1)) * t[j];
            }
            return df;
        } else {
            double[][] faults = new double[(int) N][C];
            double[] t = new double[(int) L];
            double totalFaults = 0, df = 0;

            for (int j = 0; j < L; j++) {
                t[j] = 0;
            }
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < C; j++) {
                    faults[i][j] = 0;
                }
                for (int j = 0; j < L; j++) {
                    if (!correctness[i][j]) {
                        faults[i][(int) predictions[i][j]]++;
                    }
                }
                for (int j = 0; j < C; j++) {
                    if (faults[i][j] != 0) {
                        totalFaults++;
                        t[(int) faults[i][j] - 1]++;
                    }
                }
            }
            for (int j = 0; j < L; j++) {
                t[j] = t[j] / totalFaults;
                df += ((L - j) / (L - 1)) * t[j];
            }
            return df;
        }
    }

    public double measureDistinctFailureDiversity(int im, int jm) {
        double L = 2;
            double[][] faults = new double[(int) N][C];
            double[] t = new double[(int) L];
            double totalFaults = 0, df = 0;

            for (int j = 0; j < L; j++) {
                t[j] = 0;
            }
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < C; j++) {
                    faults[i][j] = 0;
                }
                    if (!correctness[i][im]) {
                        faults[i][(int) predictions[i][im]]++;
                    }
                    if (!correctness[i][jm]) {
                        faults[i][(int) predictions[i][jm]]++;
                    }
                for (int j = 0; j < C; j++) {
                    if (faults[i][j] != 0) {
                        totalFaults++;
                        t[(int) faults[i][j] - 1]++;
                    }
                }
            }
            for (int j = 0; j < L; j++) {
                t[j] = t[j] / totalFaults;
                df += ((L - j) / (L - 1)) * t[j];
            }
            return df;
    }

    public double measureSameFault() {
        double[][] values = new double[(int) (L - 1)][(int) L];

        for (int idx1 = 0; idx1 < L - 1; idx1++) {
            for (int idx2 = idx1 + 1; idx2 < L; idx2++) {
                if (measureOutOfBag) {
                    values[idx1][idx2] = (n00sMatrix[idx1][idx2])
                            / nMatrix[idx1][idx2];
                } else {
                    values[idx1][idx2] = (n00sMatrix[idx1][idx2]) / N;
                }
            }
        }
        return avg(values);
    }

    public double measureSameFault(int idx1, int idx2) {
        if (measureOutOfBag) {
            return (n00sMatrix[idx1][idx2])
                    / nMatrix[idx1][idx2];
        } else {
            return (n00sMatrix[idx1][idx2]) / N;
        }
    }

    public double measureWeightedCountErrorCorrect() {
        double[][] values = new double[(int) (L - 1)][(int) L];

        for (int idx1 = 0; idx1 < L - 1; idx1++) {
            for (int idx2 = idx1 + 1; idx2 < L; idx2++) // N11 + .5 * (N01 + N10) - N00d - weight * N00s
            {
                if (measureOutOfBag) {
                    values[idx1][idx2] = (n11Matrix[idx1][idx2] + 0.5
                            * (n10Matrix[idx1][idx2] + n01Matrix[idx1][idx2])
                            - n00dMatrix[idx1][idx2] - weight
                            * n00sMatrix[idx1][idx2])
                            / nMatrix[idx1][idx2];
                } else {
                    values[idx1][idx2] = (n11Matrix[idx1][idx2] + 0.5
                            * (n10Matrix[idx1][idx2] + n01Matrix[idx1][idx2])
                            - n00dMatrix[idx1][idx2] - weight
                            * n00sMatrix[idx1][idx2])
                            / N;
                }
            }
        }
        return avg(values);
    }

    public double measureWeightedCountErrorCorrect(int idx1, int idx2) {
                if (measureOutOfBag) {
                    return (n11Matrix[idx1][idx2] + 0.5
                            * (n10Matrix[idx1][idx2] + n01Matrix[idx1][idx2])
                            - n00dMatrix[idx1][idx2] - weight
                            * n00sMatrix[idx1][idx2])
                            / nMatrix[idx1][idx2];
                } else {
                    return (n11Matrix[idx1][idx2] + 0.5
                            * (n10Matrix[idx1][idx2] + n01Matrix[idx1][idx2])
                            - n00dMatrix[idx1][idx2] - weight
                            * n00sMatrix[idx1][idx2])
                            / N;
                }
    }

    public Vector addDiversityMeasures(Vector vector) {
        for (Enumeration<String> enu = enumerateMeasures(); enu.hasMoreElements();) {
            vector.add(enu.nextElement());
        }
        return vector;
    }

    public Enumeration enumerateMeasures() {
        Vector newVector = new Vector(1);
        if (useCompound) {
            newVector.addElement("measureCompoundQ");
            newVector.addElement("measureCompoundP");
            newVector.addElement("measureCompoundDisagreement");
            newVector.addElement("measureCompoundDoubleFault");
            newVector.addElement("measureCompoundEntropy");
            newVector.addElement("measureCompoundInterraterK");
            newVector.addElement("measureCompoundKohaviWolpert");
            newVector.addElement("measureCompoundDifficulty");
            newVector.addElement("measureCompoundGeneralizedDiversity");
            newVector.addElement("measureCompoundCoincidenceFailureDiversity");
            newVector.addElement("measureCompoundDistinctFailureDiversity");
            newVector.addElement("measureCompoundSameFault");
            newVector.addElement("measureCompoundWeightedCountErrorCorrect");
        }
        newVector.addElement("measureQ");
        newVector.addElement("measureP");
        newVector.addElement("measureDisagreement");
        newVector.addElement("measureDoubleFault");
        newVector.addElement("measureEntropy");
        newVector.addElement("measureInterraterK");
        newVector.addElement("measureKohaviWolpert");
        newVector.addElement("measureDifficulty");
        newVector.addElement("measureGeneralizedDiversity");
        newVector.addElement("measureCoincidenceFailureDiversity");
        newVector.addElement("measureDistinctFailureDiversity");
        newVector.addElement("measureSameFault");
        newVector.addElement("measureWeightedCountErrorCorrect");
        return newVector.elements();
    }

    public int numMeasures() {
        Enumeration e = enumerateMeasures();
        int num = 0;
        while (e.hasMoreElements()) {
            num++;
            e.nextElement();
        }
        return num;
    }

    public double getMeasure(String measureName) {
        if (!initialized) {
            return 0;
        }

        if (measureName.equalsIgnoreCase("measureCompoundQ")) {
            return Compound("Q");
        } else if (measureName.equalsIgnoreCase("measureCompoundP")) {
            return Compound("P");
        } else if (measureName.equalsIgnoreCase("measureCompoundDisagreement")) {
            return Compound("Disagreement");
        } else if (measureName.equalsIgnoreCase("measureCompoundDoubleFault")) {
            return Compound("DoubleFault");
        } else if (measureName.equalsIgnoreCase("measureCompoundEntropy")) {
            return Compound("Entropy");
        } else if (measureName.equalsIgnoreCase("measureCompoundInterraterK")) {
            return Compound("InterraterK");
        } else if (measureName.equalsIgnoreCase("measureCompoundKohaviWolpert")) {
            return Compound("KohaviWolpert");
        } else if (measureName.equalsIgnoreCase("measureCompoundDifficulty")) {
            return Compound("Difficulty");
        } else if (measureName.equalsIgnoreCase("measureCompoundGeneralizedDiversity")) {
            return Compound("GeneralizedDiversity");
        } else if (measureName.equalsIgnoreCase("measureCompoundCoincidenceFailureDiversity")) {
            return Compound("CoincidenceFailureDiversity");
        } else if (measureName.equalsIgnoreCase("measureCompoundDistinctFailureDiversity")) {
            return Compound("DistinctFailureDiversity");
        } else if (measureName.equalsIgnoreCase("measureCompoundSameFault")) {
            return Compound("SameFault");
        } else if (measureName.equalsIgnoreCase("measureCompoundWeightedCountErrorCorrect")) {
            return Compound("WeightedCountErrorCorrect");
        } else if (measureName.equalsIgnoreCase("measureQ")) {
            return measureQ();
        } else if (measureName.equalsIgnoreCase("measureP")) {
            return measureP();
        } else if (measureName.equalsIgnoreCase("measureDisagreement")) {
            return measureDisagreement();
        } else if (measureName.equalsIgnoreCase("measureDoubleFault")) {
            return measureDoubleFault();
        } else if (measureName.equalsIgnoreCase("measureEntropy")) {
            return measureEntropy();
        } else if (measureName.equalsIgnoreCase("measureInterraterK")) {
            return measureInterraterK();
        } else if (measureName.equalsIgnoreCase("measureKohaviWolpert")) {
            return measureKohaviWolpert();
        } else if (measureName.equalsIgnoreCase("measureDifficulty")) {
            return measureDifficulty();
        } else if (measureName.equalsIgnoreCase("measureGeneralizedDiversity")) {
            return measureGeneralizedDiversity();
        } else if (measureName.equalsIgnoreCase("measureCoincidenceFailureDiversity")) {
            return measureCoincidenceFailureDiversity();
        } else if (measureName.equalsIgnoreCase("measureDistinctFailureDiversity")) {
            return measureDistinctFailureDiversity();
        } else if (measureName.equalsIgnoreCase("measureSameFault")) {
            return measureSameFault();
        } else if (measureName.equalsIgnoreCase("measureWeightedCountErrorCorrect")) {
            return measureWeightedCountErrorCorrect();
        } else {
            throw new IllegalArgumentException(measureName
                    + " not supported (Diversity)");
        }
    }

    class CombinatorialIterator<E> implements Iterator<Collection<E>>,
            Iterable<Collection<E>> {

        private int[] indices;
        private final int[] maxIndices;
        private final Object[] elements;

        public CombinatorialIterator(int number, Collection<? extends E> items) {
            if (items == null || number < 1 || items.size() < number) {
                throw new IllegalArgumentException(
                        "|items| >= number && number > 1");
            }
            elements = items.toArray();
            indices = new int[number];
            maxIndices = new int[number];
            for (int i = 0; i < number; i++) {
                indices[i] = i;
                maxIndices[i] = elements.length + i - indices.length;
            }
        }

        public Iterator<Collection<E>> iterator() {
            return this;
        }

        public boolean hasNext() {
            return indices != null;
        }

        @SuppressWarnings("unchecked")
        private Collection<E> createFromIndices() {
            List<E> result = new ArrayList<E>(indices.length * 2);
            for (int i = 0; i < indices.length; i++) {
                result.add((E) elements[indices[i]]);
            }
            return result;
        }

        private void incrementIndices() {
            if (indices[0] == maxIndices[0]) {
                indices = null;
                return;
            }
            for (int i = indices.length - 1; i >= 0; i--) {
                if (indices[i] != maxIndices[i]) {
                    int val = ++indices[i];
                    for (int j = i + 1; j < indices.length; j++) {
                        indices[j] = ++val;
                    }
                    break;
                }
            }
        }

        public Collection<E> next() {
            if (indices == null) {
                throw new NoSuchElementException("End of iterator");
            }
            Collection<E> result = createFromIndices();
            incrementIndices();
            return result;
        }

        public void remove() {
            throw new UnsupportedOperationException("remove");
        }
    }
}
